package net.xo68.destiny.starter.worker.handler.impl;

import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import net.xo68.destiny.contract.common.SystemConstants;
import net.xo68.destiny.contract.domain.job.TriggerParameter;
import net.xo68.destiny.core.rocksdb.RocksDBStorage;
import net.xo68.destiny.core.util.DiskUtils;
import net.xo68.destiny.core.util.TimeUtils;
import net.xo68.destiny.starter.worker.SchedulerWorkerProperties;
import net.xo68.destiny.starter.worker.enums.JobLocalConfigType;
import net.xo68.destiny.starter.worker.handler.JobLocalConfigStore;
import net.xo68.destiny.starter.worker.logger.JobLogger;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 任务本地配置存储实现
 * @author wuxie
 * @version 2020-11-24
 */
@Slf4j
public class JobLocalConfigStoreImpl implements JobLocalConfigStore {

    private static final String FILE_PREFIX="script";
    private static final String KEY_PREFIX_CONFIG="config-";
    private static final String KEY_PREFIX_SOURCE="source-";

    private static final ReentrantLock configLock = new ReentrantLock();
    private static final ReentrantLock sourceLock = new ReentrantLock();

    private final SchedulerWorkerProperties workerProperties;
    private final JobLocalConfigType localConfigType;
    private final JobLogger jobLogger;
    private final TriggerParameter triggerParameter;
    private final String sourceSuffix;
    private final String configSuffix;
    private final RocksDBStorage dbStorage;

    public JobLocalConfigStoreImpl(SchedulerWorkerProperties workerProperties, JobLocalConfigType localConfigType, JobLogger jobLogger, TriggerParameter triggerParameter,String sourceSuffix,RocksDBStorage dbStorage) {
        this(workerProperties,localConfigType,jobLogger,triggerParameter,sourceSuffix,dbStorage,".json");
    }

    public JobLocalConfigStoreImpl(SchedulerWorkerProperties workerProperties, JobLocalConfigType localConfigType, JobLogger jobLogger, TriggerParameter triggerParameter,String sourceSuffix,RocksDBStorage dbStorage, String configSuffix) {
        this.workerProperties = workerProperties;
        this.localConfigType = localConfigType;
        this.jobLogger = jobLogger;
        this.triggerParameter = triggerParameter;
        this.sourceSuffix=sourceSuffix;
        this.dbStorage=dbStorage;
        this.configSuffix = configSuffix;
    }

    @Override
    public JobLocalConfigType getConfigType() {
        return localConfigType;
    }

    @Override
    public String getSourcePath() {
        StringBuilder pathSb=new StringBuilder();
        pathSb.append(workerProperties.sourcePath())
                .append(File.separator)
                .append(FILE_PREFIX)
                .append("_")
                .append(String.valueOf(triggerParameter.getJobId()))
                .append(sourceSuffix);
        return pathSb.toString();
    }

    @Override
    public String getConfigPath() {
        StringBuilder pathSb=new StringBuilder();
        pathSb.append(workerProperties.configPath())
                .append(File.separator)
                .append(FILE_PREFIX)
                .append("_")
                .append(String.valueOf(triggerParameter.getJobId()))
                .append(configSuffix);
        return pathSb.toString();
    }

    @Override
    public void save() {
        switch (localConfigType){
            case ALL:
                saveConfig();
                saveSource();
                break;
            case CONFIG:
                saveConfig();
                break;
            case SOURCE:
                saveSource();
                break;
        }
    }

    private void saveConfig(){
        try{
            String jobConfig=triggerParameter.getJobDetailExtends().getJsonConfig();
            if(jobConfig==null){
                jobConfig = StringUtils.EMPTY;
            }

            configLock.tryLock(3L, TimeUnit.SECONDS);
            Date lastTime=dbStorage.getObject(getConfigDbKey(triggerParameter.getJobId()));
            if(!dbStorage.exist(getConfigDbKey(triggerParameter.getJobId()))
                    || !Files.exists(Paths.get(this.getConfigPath()))
                    || TimeUtils.calculateTimeSpan(lastTime,triggerParameter.getJobUpdateTime()).toNanos()>0){
                // 根据json写入到临时本地文件
                log.debug("写入任务配置脚本 {}",this.getConfigPath());
                try (PrintWriter writer = new PrintWriter(this.getConfigPath(), SystemConstants.ENCODE_DEFAULT)) {
                    writer.println(jobConfig);
                    dbStorage.put(getConfigDbKey(triggerParameter.getJobId()),triggerParameter.getJobUpdateTime());
                } catch (FileNotFoundException | UnsupportedEncodingException e) {
                    jobLogger.log("JSON 临时文件写入异常：{}",true, e.getMessage());
                }
            }
        } catch (InterruptedException e) {
            jobLogger.log("JSON 临时文件写入异常：{}",true, e.getMessage());
        }finally {
            configLock.unlock();
        }
    }
    private void saveSource(){
        String jobSource=triggerParameter.getJobDetailExtends().getSourceCode();
        if(jobSource==null){
            jobSource = StringUtils.EMPTY;
        }
        //加锁写文件
        sourceLock.lock();
        try{
            File scriptFile = new File(this.getSourcePath());
            Date lastTime=dbStorage.getObject(getSourceDbKey(triggerParameter.getJobId()));
            if (!dbStorage.exist(getSourceDbKey(triggerParameter.getJobId()))
                    || !scriptFile.exists()
                    || TimeUtils.calculateTimeSpan(lastTime,triggerParameter.getJobUpdateTime()).toNanos()>0){
//                String encoding= SystemConstants.ENCODE_DEFAULT;
//                if(Platform.isWindows()){
//                    encoding=SystemConstants.ENCODE_GBK;
//                }
                DiskUtils.writeFile(scriptFile,jobSource.getBytes(SystemConstants.ENCODE_DEFAULT),false);
                dbStorage.put(getSourceDbKey(triggerParameter.getJobId()),triggerParameter.getJobUpdateTime());
            }
        } catch (UnsupportedEncodingException e) {
            jobLogger.log(e.getMessage(),true, e.getMessage());
        } finally {
            sourceLock.unlock();
        }
    }

    @Override
    public void delete() {
        switch (localConfigType){
            case ALL:
                deleteConfig();
                deleteSource();
                break;
            case CONFIG:
                deleteConfig();
                break;
            case SOURCE:
                deleteSource();
                break;
        }
    }
    private void deleteSource(){
        try {
            sourceLock.tryLock(1L, TimeUnit.SECONDS);
            FileUtil.del(new File(this.getSourcePath()));
        } catch (InterruptedException e) {
            jobLogger.log(e.getMessage(),e);
        }
        finally {
            sourceLock.unlock();
        }
    }
    private void deleteConfig(){
        try {
            configLock.tryLock(1L, TimeUnit.SECONDS);
            FileUtil.del(new File(this.getConfigPath()));
        } catch (InterruptedException e) {
            jobLogger.log(e.getMessage(),e);
        }
        finally {
            configLock.unlock();
        }
    }

    private String getConfigDbKey(long instanceId){
        return KEY_PREFIX_CONFIG+String.valueOf(instanceId);
    }
    private String getSourceDbKey(long instanceId){
        return KEY_PREFIX_SOURCE+String.valueOf(instanceId);
    }

}
